import { ExtendedCss } from "@adguard/extended-css";
import { reportRecord, cleanRules } from "./actions.mts";
import { canApplyCss, parseBRules } from "./bRuleSupport.mts";
import { features, debug, speedOnly } from "./buildConfig.mts";
import {
  values,
  addStyle,
  gmMenu,
  getRuleFromResource,
  defaultValues,
} from "./gmBridge.mts";
import { saveCss } from "./presave.mts";
import {
  ruleToCss,
  isBasicRule,
  bRuleSpliter,
  ruleLoader,
  styleBoxes,
  CMRE,
} from "./util.mts";
import preset from "./_presets.mjs";
import { logger, speed, countLength } from "./logger.mts";
import { data } from "./globalData.mts";
import type { Rule, BRule, AbpObject } from "./types.d.mts";

/**
 * 计算自定义规则的 Hash
 * @async
 * @param {boolean} saveHash 是否存储 Hash
 * @returns {Promise.<string>} Hash
 */
export async function getCustomHash(saveHash: boolean): Promise<string> {
  if (location.protocol === "https:") {
    const hash = new Uint32Array(
      await globalThis.crypto.subtle.digest(
        "SHA-1",
        await new Blob([data.customRules]).arrayBuffer(),
      ),
    ).toString();
    debug &&
      !speedOnly &&
      logger("color", "HASH", "magenta", hash, data.customRules);
    if (saveHash) {
      await values.hash(hash);
    }
    return hash;
  } else {
    return defaultValues.hash;
  }
}

/**
 * 从各个来源收集规则
 * @async
 * @param {boolean} apply 是否立即应用规则
 * @returns {Promise.<void>} 返回空的 Promise
 */
export async function initRules(apply: boolean): Promise<void> {
  let abpRules: AbpObject = defaultValues.rules;
  data.receivedRules = "";
  if (features.fromValues)
    abpRules = (await values.rules()) ?? defaultValues.rules;
  debug && speed("@resource", "start");
  for (const rule of preset.onlineRules) {
    const resourceRule = features.fromResource
      ? await getRuleFromResource(rule.标识)
      : null;
    debug &&
      !speedOnly &&
      logger("data", {
        规则: rule.标识,
        values: abpRules[rule.标识].length,
        resource: resourceRule?.length,
      });
    if (resourceRule && !abpRules[rule.标识]) {
      abpRules[rule.标识] = resourceRule;
    }
  }
  debug && speed("@resource", "end");
  if (debug && !speedOnly) {
    logger(
      "color",
      `已存储规则: ${String(Object.keys(abpRules).length)}`,
      "royalblue",
    );
    logger("data", {
      规则: "数量",
      ...countLength(abpRules),
    });
  }
  for (const name of Object.keys(abpRules)) {
    data.receivedRules += "\n" + abpRules[name];
  }
  data.allRules = data.customRules + data.receivedRules;
  if (debug && !speedOnly) {
    logger("data", {
      自定义: data.customRules.length,
      订阅: data.receivedRules.length,
      总计: data.allRules.length,
    });
  }
  if (apply) {
    await parseRules();
  }
  return;
}

/**
 * 将所有支持的 ABP 规则筛选后转换为 CSS 和 BRule 对象
 * @async
 * @returns {Promise.<void>} 返回空的 Promise
 */
export async function parseRules(): Promise<void> {
  const bRuleSet = new Set<BRule>(),
    bRuleBad: BRule[] = [],
    hRules = [
      { rules: new Map<string, Rule>(), whites: new Set<string>() },
      { rules: new Map<string, Rule>(), whites: new Set<string>() },
      { rules: new Map<string, Rule>(), whites: new Set<string>() },
      { rules: new Map<string, Rule>(), whites: new Set<string>() },
    ] as const;
  function addRule(rule: Rule, box: number) {
    const { css, sel, isStyle } = ruleToCss(rule, data.preset, false);
    const index = (box % 2) + (rule.generic ? 0 : 2);
    const checkResult = ExtendedCss.validate(sel) as {
      ok: boolean;
      error: string | null;
    };
    const typeError = isStyle && rule.type < 2;
    if (checkResult.ok && !typeError) {
      data[styleBoxes[index]] += css;
      data.appliedCount++;
    } else {
      console.error("选择器检查错误:", rule, typeError || checkResult.error);
    }
  }
  debug && speed("转换规则", "start");
  for (const rule of data.allRules.split("\n")) {
    if (features.basicException && isBasicRule(rule)) {
      const brule = bRuleSpliter(rule);
      if (brule) {
        if (brule.bad) {
          bRuleBad.push(brule.rule);
        } else {
          bRuleSet.add(brule.rule);
        }
      }
    } else {
      const ruleObject = ruleLoader(rule);
      if (ruleObject !== undefined) {
        if (ruleObject.black === "black") {
          if (!hRules[ruleObject.type].whites.has(ruleObject.sel)) {
            hRules[ruleObject.type].rules.set(ruleObject.sel, ruleObject);
          }
        } else {
          if (hRules[ruleObject.type].rules.has(ruleObject.sel)) {
            hRules[ruleObject.type].rules.delete(ruleObject.sel);
          }
          hRules[ruleObject.type].whites.add(ruleObject.sel);
        }
      }
    }
  }
  if (features.basicException)
    for (const brule of bRuleBad) bRuleSet.delete(brule);
  if (features.basicException && features.fromValues)
    await values.brules([...bRuleSet]);
  debug &&
    !speedOnly &&
    logger("data", {
      选择器: {
        黑: hRules[0].rules.size,
        白: hRules[0].whites.size,
      },
      样式: {
        黑: hRules[1].rules.size,
        白: hRules[1].whites.size,
      },
      扩展选择器: {
        黑: hRules[2].rules.size,
        白: hRules[2].whites.size,
      },
      扩展样式: {
        黑: hRules[3].rules.size,
        白: hRules[3].whites.size,
      },
    });
  for (const [type, rules] of hRules.entries()) {
    for (const rule of rules.rules) addRule(rule[1], type);
  }
  for (const box of styleBoxes) {
    if (data[box] !== "") {
      data[box] +=
        "html > head:valid " +
        data.preset.replaceAll(/^\s{2,}/g, " ").replaceAll("\n", "");
    }
  }
  if (features.menu && features.fromValues) await gmMenu("count", cleanRules);
  debug && !speedOnly && logger("color", "规则分拣完成", "green");
  debug && speed("转换规则", "end");
  if (features.presave) await saveCss();
  if (!data.saved) {
    await styleApply();
  }
  return;
}

/**
 * 应用规则
 * @async
 * @returns {Promise.<void>} 返回空的 Promise
 */
export async function styleApply(): Promise<void> {
  if (features.basicException && (await parseBRules()) === 3) return;
  for (const type of [0, 1, 2, 3] as const) {
    if (canApplyCss(type)) {
      styleApplyExec(type);
    }
  }
  if (features.menu) await gmMenu("export", reportRecord);
  return;
}

/**
 * 应用 CSS
 * @param {number} type CSS 容器编号
 */
function styleApplyExec(type: 0 | 1 | 2 | 3): void {
  const csss = data[styleBoxes[type]].replace(CMRE, "").replaceAll("\n", "");
  if (csss !== "") {
    debug &&
      !speedOnly &&
      logger("info", "appling...", type, csss.length, csss);
    new ExtendedCss({
      styleSheet: csss,
    }).apply();
    if (!(type % 2 == 1)) {
      void addStyle(csss);
    }
  }
}
